package security;

/**
 * @projectName: java-sample
 * @description:
 * @author: Adam
 * @create: 2019-12-31 11:08
 **/

import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
import com.sun.org.apache.xml.internal.security.utils.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 对称加密:加密和解密使用相同的密钥(3 加密 3*108  传输  解密 324/108)
 *         常见的加密算法:
 *         DES:Data Encryption Standard，即数据加密标准，是一种使用密钥加密的块算法
 *         AES:Advanced Encryption Standard, 高级加密标准
 * 特点:
 *   加密速度快, 可以加密大文件
 *   密文可逆, 一旦密钥文件泄漏, 就会导致数据暴露
 *   加密后编码表找不到对应字符, 出现乱码
 *   一般结合Base64使用
 * 非对称加密:加密解密使用不同的密钥
 *         首先生成密钥对, 公钥为(5,14), 私钥为(11,14)
 *         现在A希望将原文2发送给B
 *         A使用公钥加密数据. 2的5次方mod 14 = 4 , 将密文4发送给B
 *         B使用私钥解密数据. 4的11次方mod14 = 2, 得到原文2
 *  特点:
 *    加密和解密使用不同的密钥
 *    如果使用私钥加密, 只能使用公钥解密
 *    如果使用公钥加密, 只能使用私钥解密
 *    处理数据的速度较慢, 因为安全级别高
 *  常见算法：
 *    RSA
 *    ECC
 */

public class RSADemo {

    /*代码参考原文 https://www.jianshu.com/p/213d69ac27b3*/
    public static void main(String[] args){
        String algorithm = "RSA";
        String input = "非对称加密与对称加密相比，其安全性更好：对称加密的通信双方使用相同的秘钥，如果一方的秘钥遭泄露，" +
                "那么整个通信就会被破解。而非对称加密使用一对秘钥，一个用来加密，一个用来解密，而且公钥是公开的，秘钥是自己保存的，" +
                "不需要像对称加密那样在通信之前要先同步秘钥";
        try {
            //密钥对生成器
            generateKeyToFile(algorithm, "a.pub", "a.pri");

            PublicKey publicKey = loadPublicKeyFromFile(algorithm, "a.pub");
            PrivateKey privateKey = loadPrivateKeyFromFile(algorithm, "a.pri");

            String encrypt = encrypt(algorithm, input, privateKey, 245);
            System.out.println(encrypt);

            String decrypt = decrypt(algorithm, encrypt, publicKey, 256);
            System.out.println(decrypt);

        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
      * 生成密钥对，并保存在本地文件中
      * @author jie
      * @param algorithm 算法
      * @param pubPath 公钥保存路径
      * @param priPath 私钥保存路径
      * @throws Exception
      * @return 
      * @date 2019/12/31 11:20
      */
    public static void generateKeyToFile(String algorithm,String pubPath,String priPath) throws Exception{

        //获取密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        //获取密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //获取公钥
        PublicKey publicKey = keyPair.getPublic();
        //获取私钥
        PrivateKey privateKey = keyPair.getPrivate();
        //获取byte数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        //进行base64编码
        String publicKeyString = com.sun.org.apache.xml.internal.security.utils.Base64.encode(publicKeyEncoded);
        String privteKeyString = com.sun.org.apache.xml.internal.security.utils.Base64.encode(privateKeyEncoded);
        //输出流
        FileOutputStream filePub = new FileOutputStream(pubPath);
        FileOutputStream filePri = new FileOutputStream(priPath);
        try {
            PrintStream psPub = new PrintStream(filePub);
            PrintStream psPri = new PrintStream(filePri);
            psPub.println(publicKeyString);
            psPri.println(privteKeyString);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            filePub.close();
            filePri.close();
        }
    }



    /**
      * 从文件中加载公钥
      * @author jie
      * @param algorithm 算法
      * @param filePath 公钥文件路径
      * @return
      * @date 2019/12/31 13:32
      */
    public static PublicKey loadPublicKeyFromFile(String algorithm,String filePath)throws Exception{

        //将文件内容转换为字符串
        FileReader fileReader = new FileReader(filePath);
        char[] cbuf = new char[2048];
        int hasRead = 0;
        String keyString = "";
        while ((hasRead = fileReader.read(cbuf))>0){
            keyString += new String(cbuf,0,hasRead);
        }
        return loadPublicKeyFromString(algorithm,keyString);
    }



    /**
      * 从字符串中加载公钥
      * @author jie
      * @param algorithm 算法
      * @param keyString 公钥字符串
      * @return
      * @date 2019/12/31 13:36
      */
       public static PublicKey loadPublicKeyFromString(String algorithm, String keyString) throws Exception{
           //进行Base64解码
           byte[] encode = Base64.decode(keyString);
           //获取密钥工厂
           KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
           //构建密钥规范
           X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encode);
           return keyFactory.generatePublic(keySpec);
       }


       /**
         * 从文件中加载私钥
         * @author jie
         * @param algorithm 算法
         * @param filePath 私钥文件路径
         * @return
         * @date 2019/12/31 14:00
         */
       public static PrivateKey loadPrivateKeyFromFile(String algorithm,String filePath) throws Exception {
           //将文件内容转换为字符串
           FileReader fileReader = new FileReader(filePath);
           char[] chars = new char[2048];
           int hasRead = 0;
           String keyString = "";
           while ((hasRead = fileReader.read(chars))>0){
               keyString += new String(chars,0,hasRead);
           }
           return loadPrivateKeyFromString(algorithm,keyString);
       }



       /**
         * 从字符串加载私钥
         * @author jie
         * @param
         * @return
         * @date 2019/12/31 14:38
         */
       public static PrivateKey loadPrivateKeyFromString(String algorithm, String keyString) throws NoSuchAlgorithmException, InvalidKeySpecException, Base64DecodingException {
              //进行base64解码
             byte[] decode = Base64.decode(keyString);
             //获取密钥工厂
             KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
             //构建密钥规范
             PKCS8EncodedKeySpec keySpec= new PKCS8EncodedKeySpec(decode);

             return keyFactory.generatePrivate(keySpec);
       }


       /**
         * 使用密钥加密数据
         * @author jie
         * @param algorithm 算法
         * @param input 原文
         * @param key 密钥
         * @param maxEncryptSize 最大加密长度
         * @return 密文
         * @date 2019/12/31 14:43
         * @throws Exception
         */
       public static String encrypt(String algorithm,String input,Key key,int maxEncryptSize) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException {
           // 获取Cipher对象
           Cipher cipher = Cipher.getInstance(algorithm);
           // 初始化模式(加密)和密钥
           cipher.init(Cipher.ENCRYPT_MODE,key);
           //将原文转换为byte数组
           byte[] data = input.getBytes();
           //总数据长度
           int total = data.length;
           ByteArrayOutputStream baos = new ByteArrayOutputStream();
           decodeByte(maxEncryptSize, cipher, data, total, baos);
           // 对密文进行Base64编码
           return Base64.encode(baos.toByteArray());

       }

       /**
         * 解密数据
         * @author jie
         * @param algorithm 算法
         * @param encrypted 密文
         * @param key 密钥
         * @param maxDecryptSize 最大解密长度(根据实际情况进行调整)
         * @return 原文
         * @date 2019/12/31 14:54
         */
       public static String decrypt(String algorithm,String encrypted,Key key,int maxDecryptSize) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, Base64DecodingException, BadPaddingException, IllegalBlockSizeException, IOException {
           // 获取Cipher对象
           Cipher cipher = Cipher.getInstance(algorithm);
           // 初始化模式(解密)和密钥
           cipher.init(Cipher.DECRYPT_MODE, key);
           // 由于密文进行了Base64编码, 在这里需要进行解码
           byte[] data = Base64.decode(encrypted);
           // 总数据长度
           int total = data.length;
           // 输出流
           ByteArrayOutputStream baos = new ByteArrayOutputStream();

           decodeByte(maxDecryptSize, cipher, data, total, baos);
           // 输出原文
           return baos.toString();
       }


       /**
         * 分段处理数据
         * @author jie
         * @param
         * @return
         * @date 2019/12/31 14:58
         */
       private static void decodeByte(int maxSize, Cipher cipher, byte[] data, int total, ByteArrayOutputStream baos) throws BadPaddingException, IllegalBlockSizeException, IOException {
           //偏移量
           int offset = 0;
           //缓冲区
           byte[] buffer;
           //如果数据没有处理完就一直继续s
           while (total-offset>0){
               //如果剩余的数据 >= 最大处理能力，就按照最大处理能力来加密数据
               if (total-offset>=maxSize){
                   //加密数据
                   buffer = cipher.doFinal(data,offset,maxSize);
                   // 偏移量向右侧偏移最大数据能力个
                   offset += maxSize;
               } else {
                   // 如果剩余的数据 < 最大处理能力, 就按照剩余的个数来加密数据
                   buffer = cipher.doFinal(data, offset, total - offset);
                   // 偏移量设置为总数据长度, 这样可以跳出循环
                   offset = total;
               }
               // 向输出流写入数据
               baos.write(buffer);
           }

       }
}
